/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.node;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Random;
import java.util.StringTokenizer;

import cgl.narada.protocol.NodeAddress;

public class MultipleServerNodes {

  private int numOfBrokers = 100;
  private long sleepTime=10;
  private ServerNode[] serverNodes;
  private String hostname = "machine.ucs.indiana.edu";
  private int START_FROM = 20000;
  private int portNum;
  private Random random;
  private String moduleName = "MultipleServerNodes: ";
  private int systemLevel = 3;
  private int[] levels; 
  private int[] numOfUnits;
  private Hashtable connections;
  
  public MultipleServerNodes(int numOfSSC, int numOfSC, 
			     int numOfC, int numOfB) {
    
    numOfBrokers = numOfSSC*numOfSC*numOfC*numOfB;
    portNum = START_FROM;
    try {
      hostname = InetAddress.getLocalHost().getHostName();
    } catch (Exception e) {
      hostname = "localhost";
      e.printStackTrace();
    }
    random = new Random();
    numOfUnits = new int[systemLevel+1];
    numOfUnits[0] = numOfB;
    numOfUnits[1] = numOfC;
    numOfUnits[2] = numOfSC;
    numOfUnits[3] = numOfSSC;
    
    serverNodes = new ServerNode[numOfBrokers];
    levels = new int[numOfBrokers];
    
    for (int i=0; i< numOfBrokers ; i++) {
      if (i==0) {
	serverNodes[i] = new ServerNode(true);
      } else {
	serverNodes[i] = new ServerNode(false);
      }
      initializeServerNodes(serverNodes[i]);
    } 
    
    connections = new Hashtable();
    populateLevelsInfo();
    //createNetwork();
  }
  
  private void initializeServerNodes(ServerNode serverNode) {
    Properties props = new Properties();
    props.put("NIOTCPServerPort", Integer.toString(portNum));
    serverNode.setUpNode(props, "niotcp");
    
    portNum++;
  }
  
  public void issueRequestTo(ServerNode serverNode, int port, int level) {
    if (serverNode == null) {
      System.out.println(moduleName + "Supplied server node is null");
      return;
    }
    
    Properties props = new Properties();
    props.put("hostname", hostname);
    props.put("portnum", Integer.toString(port));
    System.out.println("Initialized properties ....");
    String linkId = serverNode.createLink(props, "niotcp");
    if (linkId == null) {
      System.out.println("\n\n\n Error connecting to " + hostname + ":" 
			 + port + "\n\n");
      return;
    }
    serverNode.nodeAdditionRequest(linkId, level);

    while (serverNode.getNodeAddress(true) == null) {
      try {
	Thread.currentThread().sleep(sleepTime);
      } catch(InterruptedException ie) {
	ie.printStackTrace();
      } 
      //printNodesInfo();
    }
    
   
  }
  
  public void createNetwork() {
    int port=START_FROM;
    
    for (int i=1; i < numOfBrokers; i++) {
      //int level = random.nextInt(4);
      int level = levels[i];
      issueRequestTo(serverNodes[i], port, level); 
      System.out.println(moduleName + "Assigned address for Node[" + i + 
			 "] running on port (" + port + ") is " + 
			 serverNodes[i].getNodeAddress(true) );
      addConnBetweenNodes(i, i-1);
      port++;
    }
    
  }

  public void printNodesInfo() {
    int port = START_FROM;
    for (int i=0; i < numOfBrokers; i++) {
      System.out.println("Node [" + i + "] address assigned = " + 
			 serverNodes[i].getNodeAddress(false) + 
			 " running on port (" + port + ") is ");
      port++;
    }
  }

  public void printConnectionsInfo() {
    for (Enumeration e = connections.keys(); e.hasMoreElements();) {
      System.out.println(e.nextElement());
    }
    System.out.println("Total Number of connections in the system = " +
		       connections.size());
  }

  public void populateLevelsInfo() {
    int offset = 0;
    for (int i=0; i < numOfUnits[3]; i++) {
      for (int j=0; j < numOfUnits[2]; j++) {
	for (int k=0; k < numOfUnits[1]; k++) {
	  for (int l =0; l < numOfUnits[0]; l++) {
	    if (updateLevelsIndicator(offset, 0)) {
	      offset++;
	    } else {
	      break;
	    }
	  }/** l */
	  if (updateLevelsIndicator(offset, 1)) {
	    offset++;
	  } else {
	    break;
	  }
	} /** k */
	if (updateLevelsIndicator(offset, 2)) {
	  offset++;
	} else {
	  break;
	}
	
      } /** j **/
      if (updateLevelsIndicator(offset, 3)) {
	offset++;
      } else {
	break;
      }
    } /** k */
   
    System.out.print("[");
    for (int i=0; i< numOfBrokers; i++) {
      System.out.print(levels[i] + " ,");
    }
    System.out.println("}");

  }
  
  private boolean updateLevelsIndicator(int offset, int value) {
    if (offset >= numOfBrokers) {
      return false;
    }
    levels[offset] = value;
    return true;
  }


  public void createConnections(int numOfConnections) {
    int createdConns = 0;

    int posA;
    int posB;

    while (createdConns != numOfConnections) {
      posA = random.nextInt(numOfBrokers);
      posB = random.nextInt(numOfBrokers);
      
      if (!determineIfAlreadyConnected(posA, posB)) {
	int suggestedLevel = determineLevelOfConnection(posA, posB);
	if (suggestedLevel >=0) {
	  issueGatewayCreationRequestBetween(posA, posB, suggestedLevel);
	  addConnBetweenNodes(posA, posB);
	  createdConns++;
	} 
       
      } /** determine if(connected) */
    } /** while(numConnections) */
    
  }




  private void issueGatewayCreationRequestBetween(int posA, int posB, 
						  int suggestedLevel) {
    int port = START_FROM + posB;
    ServerNode serverNode = serverNodes[posA];
    Properties props = new Properties();
    props.put("hostname", hostname);
    props.put("portnum", Integer.toString(port));
    System.out.println("Initialized properties ....");
    String linkId = serverNode.createLink(props, "niotcp");
    if (linkId == null) {
      System.out.println("\n\n\n Error connecting to " + hostname + ":" 
			 + port + "\n\n");
      return;
    }
    
    serverNode.gatewayCreationRequest(linkId, suggestedLevel);

  }

  

  public int determineLevelOfConnection(int posA, int posB) {
    String connKey = "";
    NodeAddress nodeA = serverNodes[posA].getNodeAddress(true);
    NodeAddress nodeB = serverNodes[posB].getNodeAddress(true);
    int[] addrA = nodeA.getAddressInInts();
    int[] addrB = nodeB.getAddressInInts();
    
    int connLevel = 0;
    for (int i =systemLevel; i > 0 ; i --) {
      if (addrA[systemLevel-i] != addrB[systemLevel-i]) {
	connLevel = i;
	break;
      }
    }
    System.out.println("Connection between Node:" + nodeA + " and Node:" 
		       +   nodeB + " should be of level (" + connLevel + ")");
    return connLevel;
  }

  /** This method returns true only if both the nodes have valid assigned
      addresses and the connection had not been previously established */
  public boolean determineIfAlreadyConnected(int posA, int posB) {
    String connKey = "";
    NodeAddress nodeA = serverNodes[posA].getNodeAddress(true);
    NodeAddress nodeB = serverNodes[posB].getNodeAddress(true);
    

    if (nodeA == null || nodeB == null) {
      System.out.println("Node[" + posA + "] address = " +  
			 serverNodes[posA].getNodeAddress(false) + 
			 "    Node[" + posB + "] address = " +  
			 serverNodes[posB].getNodeAddress(false) );
      return true;
    }

    String keyA = nodeA.toString();
    String keyB = nodeB.toString();

    if (posA > posB) {
      connKey = keyA + "<->" + keyB;
    } else {
      connKey = keyB + "<->" + keyA;
    }
    
    if (!connections.containsKey(connKey)) {
      return false;
    }    
    return true;    
  }

  
  private void addConnBetweenNodes(int posA, int posB) {
    String connKey = "";
    NodeAddress nodeA = serverNodes[posA].getNodeAddress(true);
    NodeAddress nodeB = serverNodes[posB].getNodeAddress(true);
    

    if (nodeA == null || nodeB == null) {
      System.out.println("Node[" + posA + "] address = " +  
			 serverNodes[posA].getNodeAddress(false) + 
			 "    Node[" + posB + "] address = " +  
			 serverNodes[posB].getNodeAddress(false) );
      return;
    }

    String keyA = nodeA.toString();
    String keyB = nodeB.toString();

    if (posA > posB) {
      connKey = keyA + "<->" + keyB;
    } else {
      connKey = keyB + "<->" + keyA;
    }
    
    if (!connections.containsKey(connKey)) {
      connections.put(connKey, connKey);
    }

  }
  
  public static void main(String[] args) {
    if (args.length != 4) {
      System.out.println("Usage:\t\tjava cgl.narada.test.MultipleServerNodes" 
			 + "<numOfSSC> <numOfSC> <numOfC> <numOfB>");
      System.exit(0);
    }
    int numOfSSC = Integer.parseInt(args[0]);
    int numOfSC = Integer.parseInt(args[1]);
    int numOfC = Integer.parseInt(args[2]);
    int numOfB = Integer.parseInt(args[3]);
    
    MultipleServerNodes multipleNodes = 
      new MultipleServerNodes(numOfSSC, numOfSC, numOfC, numOfB);
    BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
    String cmd;
    StringTokenizer tok;
    while (true) {
      try {
	cmd = r.readLine();
	if(cmd.startsWith("h")) {
	  System.out.println("ls \t\t\t-> List all Broker Node Addresses ");
	}
	
	if (cmd == null) {
	  break;
	}
	
	if (cmd.equals("cr")) {
	  multipleNodes.createNetwork();
	  continue;
	}

	if (cmd.startsWith("ls")) {
	  multipleNodes.printNodesInfo();
	  continue;
	}
	
	if (cmd.startsWith("lc")) {
	  multipleNodes.printConnectionsInfo();
	  continue;
	}
	
	if (cmd.startsWith("crc")) {
	  tok = new StringTokenizer(cmd, " ");
	  tok.nextToken();
	  multipleNodes.createConnections(Integer.parseInt(tok.nextToken()));
	  continue;
	}
	
      }catch (IOException ioe) {
	ioe.printStackTrace();
      } /*end try-catch */

     }/*end while(true) */
  }/* end main */
}
